package cn.wen.java.sql;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;

import javax.sql.DataSource;

import org.apache.log4j.Logger;

import cn.wen.bases.Constants;
import cn.wen.java.sql.ds.DSHelper;
import cn.wen.java.sql.ds.Datastore;
import cn.wen.java.sql.utils.UtilsOfDB;

/**
 * Title：数据操作中心
 * <p>
 * Description：
 * <p>
 * Copyright：Copyright (c) 2010-2012
 * 
 * @author <a href="mailto:ct_wen@qq.com">ctwen</a>, 温承瑭
 * @version Revision：1.0 Date：2010/12/1 Description：未完成。
 *          <p>
 *          Revision：1.5 Date：2010/12/3 Description：实现getResultSet，update等方法……
 *          <p>
 *          Revision：1.6 Date：2010/12/10 Description：修正初始化方法，获取数据源，事务处理，资源释放……
 *          <p>
 *          Revision：2.0 Date：2010/05/04 Description：修检索方法，资源释放……
 *          <p>
 *          Revision：2.5 Date：2010/08/01 Description：添加对存储过程相关支持。
 */
public class DataCenter {

    public DataCenter() {
	super();
	// initialized(); //初始化……
    }

    public DataCenter(DataSource dataSource) {
	super();
	this.dataSource = dataSource;
	// initialized(); //初始化……
    }

    private final Logger log = Logger.getLogger(getClass());

    /**
     * 数据源
     */
    protected DataSource dataSource = null;

    /**
     * 库连接对象
     */
    private Connection conn = null;

    /**
     * 执行SQL，检索出数据集并返回。 <br>
     * 注：执行处理之后切记关闭资源！请调用：destroyed方法。
     * 
     * @param sql SQL语句
     * @param params 参数列表
     * @return 数据集
     */
    public Datastore query(String sql, Object... params) {
	return getDatastore(1, sql, params);
    }

    /**
     * 执行存储过程，检索出数据集并返回。 <br>
     * 调用过程名：{call procName(?, ?)} ; 执行过程体：BEGIN …… END; <br>
     * 注：执行处理之后切记关闭资源！请调用：destroyed方法。
     * 
     * @param proc 过程名
     * @param params 参数列表
     * @return 数据集
     */
    public Datastore queryByStoreProc(String proc, Object... params) {
	return getDatastore(0, proc, params);
    }

    /**
     * 检索出数据集并返回 <br>
     * 注：执行处理之后切记关闭资源！请调用：destroyed方法。
     * 
     * @param type sql类型 0 => Procedure，1 => SQL
     * @param sql sql
     * @param params 参数列表
     * @return 数据集
     * @throws SQLException
     */
    protected Datastore getDatastore(Integer type, String sql, Object... params) {
	Datastore ds = null;
	PreparedStatement pstat = null;
	ResultSet rs = null;

	try {
	    pstat = getPreparedStatement(type, sql, params);
	    ds = DSHelper.createDatastoreByResultSet(rs = pstat.executeQuery());
	}
	catch (SQLException e) {
	    log.error(e.getMessage(), e);
	}
	finally {
	    close(rs, pstat);
	}

	return ds;
    }

    /**
     * 执行SQL，并返回影响行数 <br>
     * 注：执行处理之后切记关闭资源！请调用：destroyed方法。
     * 
     * @param sql sql
     * @param params 参数列表
     * @return 影响行数
     */
    public Integer update(String sql, Object... params) {
	return update(1, sql, params);
    }

    /**
     * 执行存储过程，并返回影响行数 <br>
     * 注：执行处理之后切记关闭资源！请调用：destroyed方法。
     * 
     * @param sql sql
     * @param params 参数列表
     * @return 影响行数
     */
    public Integer updateByStoreProc(String sql, Object... params) {
	return update(0, sql, params);
    }

    /**
     * 更新数据并返回影响行数 <br>
     * 注：执行处理之后切记关闭资源！请调用：destroyed方法。
     * 
     * @param type sql类型
     * @param sql sql
     * @param params 参数列表
     * @return 影响行数
     */
    protected Integer update(Integer type, String sql, Object... params) {
	Integer rows = 0;
	PreparedStatement pstat = null;

	try {
	    pstat = getPreparedStatement(type, sql, params);
	    rows = pstat.executeUpdate();
	}
	catch (SQLException e) {
	    rollback(); // 事务回滚
	    log.error(e.getMessage(), e);
	}
	finally {
	    close((ResultSet) null, pstat);
	}
	return rows;
    }

    /**
     * 返回type类型的预处理对象
     * 
     * @param type 0 => Procedure，1 => SQL
     * @param sql
     * @return
     * @throws SQLException
     */
    protected PreparedStatement getPreparedStatement(Integer type, String sql, Object[] params) throws SQLException {
	PreparedStatement pstat = null;

	switch (type) {
	case 0:
	    // 调用过程名：{call procName(?, ?)} ; 执行过程体：BEGIN …… END;
	    pstat = getConnection().prepareCall(sql);
	    break;
	case 1:
	    pstat = getConnection().prepareStatement(sql);
	    break;
	default:
	    throw new SQLFeatureNotSupportedException("未知sql类型。");
	}
	// 设置参数
	setParams(pstat, params);
	return pstat;
    }

    /**
     * 参数设置
     * 
     * @param pstmt 预处理对象
     * @param params 参数列表
     * @return
     * @throws SQLException
     */
    protected int setParams(PreparedStatement pstmt, Object... params) throws SQLException {
	int i = 0;
	while (i < params.length) {
	    pstmt.setObject(i + 1, params[i++]);
	}
	return i;
    }

    /**
     * 设置是否自动提交事务
     * 
     * @param commit
     */
    public void setAutoCommit(boolean commit) {
	try {
	    this.conn.setAutoCommit(commit);
	}
	catch (SQLException e) {
	    this.log.error(e.getMessage(), e);
	}
    }

    /**
     * 事务提交
     */
    public void commit() {
	transaction(1);
    }

    /**
     * 事务回滚
     */
    public void rollback() {
	transaction(0);
    }

    /**
     * 事务处理
     * 
     * @param flag 事务类型
     */
    private void transaction(Integer flag) {
	try {
	    switch (flag) {
	    case 0:
		this.conn.rollback();
		break;
	    case 1:
		this.conn.commit();
		break;
	    }
	}
	catch (SQLException e) {
	    this.log.error(e.getMessage(), e);
	}
    }

    public DataSource getDataSource() {
	return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
	this.dataSource = dataSource;
    }

    public Connection getConnection() {
	if (this.conn != null) {
	    return this.conn;
	}
	try {
	    return this.conn = dataSource.getConnection();
	}
	catch (SQLException e) {
	    log.error(e.getMessage(), e);
	}
	return this.conn;
    }

    /**
     * 关闭资源
     */
    protected void close(ResultSet rs, Statement stat) {
	try {
	    if (rs != null) { // rs.isClosed() C3P0报错。
		rs.close();
	    }
	    if (stat != null) { // stat.isClosed() C3P0报错。
		stat.close();
	    }
	}
	catch (SQLException e) {
	    this.log.error(e.getMessage(), e);
	}
    }

    /**
     * 初始化数据访问资源（包括：数据源、库连接对象。）
     */
    public void initialized() {
	if (this.dataSource == null) {
	    this.dataSource = UtilsOfDB.getDataSource();
	}

	this.conn = getConnection();
	this.log.debug("DataCenter initialized(Open connection).");
    }

    /**
     * <pre>
     * 销毁数据访问资源（库连接对象）。资源被释放后若再想使用，初始化: initialized();
     * </pre>
     * 
     * @see #initialized()
     */
    public void destroyed() {
	UtilsOfDB.disconnect(conn);
	conn = null;
	this.log.debug("DataCenter destroyed(Close connection).");
    }

    // ---------------------------------------------------------------------
    // 分页相关
    // ---------------------------------------------------------------------

    private final Pagination pagination = new Pagination();

    /**
     * 分页器
     */
    public Pagination getPagination() {
	return pagination;
    }

    /**
     * Title：数据操作中心>>拓展类（分页支持）
     * <p>
     * Description：与分页相关的操作。
     * <p>
     * Copyright：Copyright (c) 2011
     * 
     * @author <a href="mailto:ct_wen@qq.com">ctwen</a>, 温承瑭
     * @version Revision：1.0 Date：2011/9/24 Description：初次完成。
     *          <p>
     */
    protected class Pagination {

	/**
	 * <pre>
	 * 执行SQL，检索出数据集并返回，支持分页。
	 * 
	 * 注：
	 * 1.执行处理之后切记关闭资源！请调用：destroyed方法。
	 * 2.每页记录数是：应用默认值 --> "app.grid.pagination.row.size"。
	 * 3.总记录数是：自动统计。
	 * </pre>
	 * 
	 * @param sql 语句
	 * @param pagiNum 页码
	 * @param params 语句参数
	 * @return 数据集
	 */
	public Datastore query(String sql, int pagiNum, Object... params) {
	    return query(sql, pagiNum, 0, 0, params);
	}

	/**
	 * <pre>
	 * 执行SQL，检索出数据集并返回，支持分页设置。
	 * 
	 * 注：
	 * 1.执行处理之后切记关闭资源！请调用：destroyed方法。
	 * 2.总记录数是：自动统计。
	 * </pre>
	 * 
	 * @param sql 语句
	 * @param pagiNum 页码
	 * @param pagiRowNum 每页记录数（若为：0，则使用应用默认值 --> "app.grid.pagination.row.size"。）
	 * @param params 语句参数
	 * @return 数据集
	 */
	public Datastore query(String sql, int pagiNum, int pagiRowNum, Object... params) {
	    return query(sql, pagiNum, pagiRowNum, 0, params);
	}

	/**
	 * <pre>
	 * 执行SQL，检索出数据集并返回，支持分页设置。
	 * 
	 * 注：执行处理之后切记关闭资源！请调用：destroyed方法。
	 * </pre>
	 * 
	 * @param sql 语句
	 * @param pagiNum 页码
	 * @param pagiRowNum 每页记录数（若为：0，则使用应用默认值 --> "app.grid.pagination.row.size"。）
	 * @param pagiRowCount 总记录数（若为：0，则统计一遍语句分页前的总记录数。）
	 * @param params 语句参数
	 * @return 数据集
	 */
	public Datastore query(String sql, int pagiNum, int pagiRowNum, int pagiRowCount, Object... params) {
	    if (pagiRowCount == 0) { // 统计分页前的总记录数。
		pagiRowCount = getDatastore(1, "\nSELECT COUNT(1) ROW_COUNT FROM (" + sql + ") /*no-output*/", params)
			.get(0, 0).toInteger();
	    }
	    if (pagiRowNum == 0) { // 应用默认值的每页记录数。
		pagiRowNum = Constants.APP_GRID_PAGINATION_ROW_SIZE;
	    }

	    // 分页SQL
	    sql = "\nSELECT * FROM ( SELECT PAGI999TABLE.*, ROWNUM PAGI999ROWNUM FROM (\n" + sql +
	    // 本页最后记录的行号
		    "\n) PAGI999TABLE WHERE ROWNUM < " + ((pagiNum - 1) * pagiRowNum + pagiRowNum + 1) + ") " +
		    // 本页起始记录的行号
		    "WHERE PAGI999ROWNUM > " + ((pagiNum - 1) * pagiRowNum);

	    Datastore ds = getDatastore(1, sql, params);
	    return ds;
	}

    }

    // ---------------------------------------------------------------------
    // 存储过程相关
    // ---------------------------------------------------------------------

    private final Procedure procedure = new Procedure();

    /**
     * 数据操作中心>>拓展类（存储过程）
     * 
     * @see Procedure
     */
    public Procedure getProcedure() {
	return procedure;
    }

    /**
     * Title：数据操作中心>>拓展类（存储过程）
     * <p>
     * Description：与存储过程相关的操作。
     * <p>
     * Copyright：Copyright (c) 2011
     * 
     * @author <a href="mailto:ct_wen@qq.com">ctwen</a>, 温承瑭
     * @version Revision：1.0 Date：2011/7/27 Description：初次完成。
     *          <p>
     */
    protected class Procedure {

	private final Logger log = Logger.getLogger(getClass());

	/**
	 * 执行存储过程，并返回输出参数。
	 * 
	 * @param sql 过程体
	 * @param outParamsType 参数类型
	 * @return 输出参数
	 * @see java.sql.Types
	 */
	public Object[] execute(String sql, int[] outParamsType) {
	    Object[] vals = new Object[outParamsType.length];
	    CallableStatement call = null;

	    try {
		call = getConnection().prepareCall(sql);
		// 注册输出参数
		for (int i = 0; i < outParamsType.length; i++) {
		    call.registerOutParameter(i + 1, outParamsType[i]);
		}
		call.execute(); // 执行过程……
		// 获取参数结果
		for (int i = 0; i < vals.length; i++) {
		    vals[i] = call.getObject(i + 1);
		}
	    }
	    catch (SQLException e) {
		log.error(e.getMessage(), e);
	    }
	    finally {
		close((ResultSet) null, call);
	    }

	    return vals;
	}

    }

}
